#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
#include "../../../clsv/error.h"
#include "../transADT.h"
#include "tcpADT.h"

/*--------------------------------------------------------------------------*/

#define TCP_SND 0
#define TCP_RCV 1

/*--------------------------------------------------------------------------*/

struct tcpCDT
{
	int sockId; /* Message queue key value */
	struct addrinfo *host; /* Host addres information */
};

/*--------------------------------------------------------------------------*/

static int createSock(void);

static int initTcpADT(const char *port, const char *host, tcpADT *tcp);

static int comm(int sockId, void *buf, int count, int receive, int timeO);

static void doNothing(int s);

/*--------------------------------------------------------------------------*/

extern int errno;
static int interrupted;

/*--------------------------------------------------------------------------*/


/* Common initialization of tcpADT */ 
int
initTcpADT(const char *host, const char *port, tcpADT *tcp)
{
	tcpADT aux;
	struct addrinfo hints;
	struct addrinfo *res;
	int ans;
	int again = 0;

	*tcp = NULL;

	if ( (aux = malloc(sizeof(struct tcpCDT)) ) == NULL )
	{
		fprintf(stderr,"tcpADT: Initiating sockADT, malloc error.\n");
		return ERROR_MALLOC; /* Not enough memory */
	}

	aux->sockId = -1;
	aux->host = NULL;

	/* Fill host structure? */
	if( port != NULL )
	{
		memset(&hints, 0, sizeof(hints));

		/* TODO: IPv4 and IPv6 support */
		/* hints.ai_family = PF_UNSPEC; */	
		hints.ai_family = PF_INET;
		hints.ai_socktype = SOCK_STREAM;
		
		if( host == NULL ) /* Server */
		{
			hints.ai_flags = AI_PASSIVE;
		}
		
		/* Get addres info */
		do
		{
			again = 0;
			/* TODO: Check EAI_MEMORY (like malloc) */
			ans = getaddrinfo(host, port, &hints, &res);
			if( ans )
			{	
				if( errno == EAI_AGAIN )
				{
					again = 1;
				}
				else
				{
					fprintf(stderr,"tcpADT: Initiating sockADT.\n");
					fprintf(stderr,"%s\n",gai_strerror(ans));
					free(aux);
					return ERROR_UNEX;
				}
			}
			else
			{
				aux->host = res;
			}			
			
		}while( again );
	}

	*tcp = aux;
	
	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

/* Create a new queue */
/* Returns the queue ID or less than zero if error */
static int
createSock(void)
{
	int error = ERROR_OK;
	
	if( (error = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{	
		if( errno == EACCES ) /* Queue already exists */
		{	
			perror("tcpADT: Creating socket, acces denied, check queue permissions.\n");
			error = ERROR_ACCES;
		}
		else /* Not enough memory or system limit reached */
		{	
			perror("tcpADT: Creating socket, unexpected error.\n");
			error = ERROR_UNEX;
		}
	}
	
	return error;
}

/*--------------------------------------------------------------------------*/

int
connTcp(const char *host, const char *port, tcpADT *tcp)
{
	int ans;
	int error = ERROR_OK;
		
	if( port == NULL || host == NULL )
		return ERROR_PARAMS;

	/* Common initialization */
	ans = initTcpADT(host, port, tcp);
	if( ans != ERROR_OK )
		return ans;
	
	/* Create socket */
	if( (ans = createSock()) < 0 )
	{
		error = ans;
	}
	else
	{	/* Save the sock ID */
		(*tcp)->sockId = ans;
		/* TODO: Check EINTR, interrupted by a signal. */
		
		/* Connect the socket */
		ans = connect((*tcp)->sockId, (*tcp)->host->ai_addr, (*tcp)->host->ai_addrlen);
		if( ans == -1)
		{	
			if( errno == ETIMEDOUT)
			{
				perror("tcpADT: Connecting, timeout.\n");
				error = ERROR_TIMEOUT;
			}
			else
			{
				perror("tcpADT: Connecting, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}
	}
	
	if( error != ERROR_OK )
	{
		closeTcp(*tcp);
		*tcp = NULL;
	}

	return error;
}

/*--------------------------------------------------------------------------*/

/* Create a new queue to start listening */
int
bindTcp(const char *port, tcpADT *tcp)
{
	int ans;
	int error = ERROR_OK;
	
	if( port == NULL )
		return ERROR_PARAMS;
	
	/* Common initialization */
	error = initTcpADT(NULL, port, tcp);
	if( error != ERROR_OK )
		return error;
	
	/* Create socket */
	if( (ans = createSock()) < 0 )
	{
		error = ans;
	}
	else
	{	/* Save the sock ID */
		(*tcp)->sockId = ans;
	
		/* TODO: Check ENOMEM (like malloc) */
		ans = bind((*tcp)->sockId, (*tcp)->host->ai_addr, (*tcp)->host->ai_addrlen);
		if( ans == -1)
		{	
			if( errno == EACCES )
			{
				perror("tcpADT: Binding, acces denied, check permissions.\n");
				error = ERROR_ACCES;
			}
			else if( errno == EADDRINUSE )
			{
				perror("tcpADT: Binding, port in use.\n");
				error = ERROR_EXIST;				
			}
			else
			{
				perror("tcpADT: Binding, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}
		else
		{
			ans = listen((*tcp)->sockId, SOMAXCONN);
			if( ans == -1 )
			{
				if( errno == EADDRINUSE)
				{
					perror("tcpADT: Binding, port in use.\n");
					error = ERROR_EXIST;
				}
				else
				{
					perror("tcpADT: Binding, unexpected.\n");
					error = ERROR_UNEX;
				}
			}
		}
		
	}
	
	if( error != ERROR_OK )
	{
		closeTcp(*tcp);
		*tcp = NULL;
	}
	
	return error;

}

/*--------------------------------------------------------------------------*/

int
acceptTcp(tcpADT tcp, tcpADT *new)
{
	int size;
	int signal2 = 0;
	int ans;
	int error = ERROR_OK;
	
	*new = NULL;

	/* Common initialization */
	error = initTcpADT(NULL, NULL, new);
	if( error != ERROR_OK )
		return error;

	do
	{
		signal2 = 0;

		ans = accept(tcp->sockId, NULL, (socklen_t *)&size);

		if( ans == -1 )
		{
			if( errno == EINTR )
			{
				signal2 = 1;
			}
			else
			{
				perror("tcpADT: Accept, unexpected.\n");
				error = ERROR_UNEX;
			}
		}
	}while( signal2 );
	
	(*new)->sockId = ans;
	return error;
}

/*--------------------------------------------------------------------------*/

/* Returns the number of bytes actually copied into buf, or less than zero */
static int
comm(int sockId, void *buf, int count, int action, int timeO)
{
	int signal2 = 0;
	int ans;
	int bytesLeft;
	int error = ERROR_OK;
	
	/* Check received (or sent) bytes and recall if necesary. */
	/* TCP_CORK flag can be used but is not portable */
	bytesLeft = count;
	do
	{
		signal2 = 0;

		if(timeO > 0)
			setAlarm(timeO, doNothing);

		if( action == TCP_RCV )
		//{
			//printf("ME TRABO?\n");
			ans = recv(sockId, (char*)buf + (count - bytesLeft), bytesLeft, 0);
			//printf("NO\n");}
		else
			ans = send(sockId, (char*)buf + (count - bytesLeft), bytesLeft, 0);
			
		if(timeO > 0)
			unsetAlarm();

		if( ans == -1 )
		{
			bytesLeft = 0;
			if( errno == EINTR)
			{
				signal2 = 1;
			}
			else
			{
				if( action == TCP_RCV )
					perror("tcpADT: Receiving, unexpected error.\n");
				else
					perror("tcpADT: Send, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}
		else
		{
			bytesLeft -= ans;
			error = count - bytesLeft;
		}

	}while( (signal2 || bytesLeft) );
//	signal(SIGALRM, SIG_IGN);

	return error;
}

/*--------------------------------------------------------------------------*/

/* Returns the number of bytes actually copied into buf, or less than zero */
int
rcvTcp(tcpADT tcp, void *buf, int count, int timeO)
{
	/* Check parameters */
	/* Count */
	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;
	
	return comm(tcp->sockId, buf, count, TCP_RCV, timeO);
}

/*--------------------------------------------------------------------------*/

/* Returns the number of bytes actually copied into buf, or less than zero */
int
sndTcp(tcpADT tcp, void *buf, int count)
{
	/* Check parameters */
	/* Count */
	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;
	
	return comm(tcp->sockId, buf, count, TCP_SND, 0);
}

/*--------------------------------------------------------------------------*/

int closeTcp(tcpADT tcp)
{
	int signal2 = 0;
	int ans;
	int error = ERROR_OK;

	if( tcp != NULL )
	{
		/* Has an open socket ? */
		if( tcp->sockId >= 0 )
		{
			do
			{
				signal2 = 0;
				ans = close(tcp->sockId);
				if( ans == -1)
				{
					if( errno == EINTR )
					{
						signal2 = 1;
					}
					else
					{
						perror("tcpADT: Closing, unexpected error\n");
						error = ERROR_UNEX;
					}
				}
			}
			while( signal2 );
		}
	
		/* Has created a host structure ? */
	 	if( tcp->host != NULL )
	 		freeaddrinfo(tcp->host);
		
		free(tcp);
	}
	
	return error;

}

/*--------------------------------------------------------------------------*/

static void
doNothing(int s)
{
	fprintf(stderr,"tcpADT: %d signal received.\n", s);
	interrupted = 1;
}

